package com.fz.us.admin.base.dao.impl;

import com.fz.us.admin.base.bean.search.Search;
import com.fz.us.admin.base.bean.search.SearchEnum;
import com.fz.us.admin.base.bean.search.order.Order;
import com.fz.us.admin.base.bean.search.page.PageList;
import com.fz.us.admin.base.bean.search.page.PagePaging;
import com.fz.us.admin.base.bean.search.page.PageUnique;
import com.fz.us.admin.base.bean.search.projection.ProjectionAggregate;
import com.fz.us.admin.base.bean.search.projection.ProjectionCount;
import com.fz.us.admin.base.bean.search.projection.ProjectionNone;
import com.fz.us.admin.base.bean.search.projection.ProjectionRowCount;
import com.fz.us.admin.base.bean.search.result.*;
import com.fz.us.admin.base.bean.search.rule.*;
import com.fz.us.admin.base.dao.MongoBaseDao;
import com.fz.us.admin.base.entity.MongoEntity;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Repository;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import static org.springframework.data.mongodb.core.query.Criteria.where;

/**
 * Dao实现类 - mongodb
 * ============================================================================
 * 版权所有 2016 。
 *
 * @author fallenpanda
 * @version 1.0 2016-07-19 。
 * ============================================================================
 */
@Repository
public abstract class MongoBaseDaoImpl<T extends MongoEntity, PK extends Serializable> implements MongoBaseDao<T, PK> {

    private static final int DEFAULT_SKIP = 0;
    private static final int DEFAULT_LIMIT = 200;

    private Class<T> entityClass;

    public MongoBaseDaoImpl(MongoTemplate mongoTemplate){
        this.mongoTemplate = mongoTemplate;
    }

    /**
     * spring mongodb　集成操作类　
     */
    @Resource
    protected MongoTemplate mongoTemplate;

    @SuppressWarnings("unchecked")
    public MongoBaseDaoImpl() {
        this.entityClass = null;
        Class<?> c = getClass();
        Type type = c.getGenericSuperclass();//返回本类的父类,包含泛型参数信息
        if (type instanceof ParameterizedType) {
            Type[] parameterizedType = ((ParameterizedType) type)
                    .getActualTypeArguments();//泛型参数的实际类型
            this.entityClass = (Class<T>) parameterizedType[0];//第一个泛型
        }
    }

    protected Class<T> getEntityClass() {
        return entityClass;
    }

    public List<T> find(Query query) {
        return mongoTemplate.find(query, entityClass);
    }

    public T findOne(Query query) {
        return mongoTemplate.findOne(query, entityClass);
    }

    public void update(Query query, Update update) {
        mongoTemplate.findAndModify(query, update, entityClass);
    }

    public T save(T entity) {
        mongoTemplate.insert(entity);
        return entity;
    }

    public T findById(String id) {
        System.out.println(entityClass);
        return mongoTemplate.findById(id, entityClass);
    }

    public T findById(String id, String collectionName) {
        return mongoTemplate.findById(id, entityClass, collectionName);
    }

    public long count(Query query) {
        return mongoTemplate.count(query, entityClass);
    }

    public SearchResult findBySearch(Search search) {
        if (search == null) {
            search = Search.simpleSearchList();
        }

        Query query = new Query();
        // 添加查询条件
        initRules(query, search.getRules());

//        List<AggregationOperation> aggregationOperations = new ArrayList<AggregationOperation>();
//        // 添加查询条件
//        initRules(aggregationOperations, search.getRules());

        if (search.getPage() instanceof PageUnique) {
            if (search.getPage().getProjection() instanceof ProjectionNone) {
                return SearchResultUnique.uniqueResult(search, mongoTemplate.findOne(query, entityClass));
            } else if (search.getPage().getProjection() instanceof ProjectionRowCount) {
                return SearchResultCount.countResult(search, Long.parseLong(String.valueOf(mongoTemplate.count(query, entityClass))));
            } else if (search.getPage().getProjection() instanceof ProjectionCount) {
                String field = ((ProjectionCount) search.getPage().getProjection()).getField();

                if (SearchEnum.ProjectionType.count.equals(search.getPage().getProjection().getProjectionType())) {
                    Assert.state(true, "mongodb 不支持该条件查询 field count");
                } else if (SearchEnum.ProjectionType.countDistinct.equals(search.getPage().getProjection().getProjectionType())) {
                    Assert.state(true, "mongodb 不支持该条件查询 field countDistinct");
//                    return SearchResultCount.countResult(search, (long) mongoTemplate.getCollection(entityClass.getSimpleName().toLowerCase()).distinct(field, query.getQueryObject()).size());
                }
            } else if (search.getPage().getProjection() instanceof ProjectionAggregate) {
                String field = ((ProjectionAggregate) search.getPage().getProjection()).getField();

                if (SearchEnum.ProjectionType.max.equals(search.getPage().getProjection().getProjectionType())) {
                    Assert.state(true, "mongodb 不支持该条件查询 field max");
                } else if (SearchEnum.ProjectionType.min.equals(search.getPage().getProjection().getProjectionType())) {
                    Assert.state(true, "mongodb 不支持该条件查询 field min");
                } else if (SearchEnum.ProjectionType.avg.equals(search.getPage().getProjection().getProjectionType())) {
                    Assert.state(true, "mongodb 不支持该条件查询 field avg");
                } else if (SearchEnum.ProjectionType.sum.equals(search.getPage().getProjection().getProjectionType())) {
                    Assert.state(true, "mongodb 不支持该条件查询 field sum");
                }
            }
        } else if (search.getPage() instanceof PageList) {
            if (search.getPage() instanceof PagePaging) {
                // 计算总记录数
                long totalCount = mongoTemplate.count(query, entityClass);

                // 添加排序
                initOrder(query, search.getOrder());

                // 添加分页
                initPager(query, (PagePaging) search.getPage());

                List results = mongoTemplate.find(query, entityClass);
                if (results == null) {
                    results = new ArrayList();
                }

                long pageCount = 0;
                Integer pageSize = ((PagePaging) search.getPage()).getPageSize();

                if (totalCount % pageSize == 0) {
                    pageCount = totalCount / pageSize;
                } else {
                    pageCount = (totalCount / pageSize) + 1;
                }

                return SearchResultPaging.list(search, ((PagePaging) search.getPage()).getPageNumber(), pageSize, totalCount, pageCount, results);
            } else {
                // 添加排序
                initOrder(query, search.getOrder());

                List results = mongoTemplate.find(query, entityClass);
                if (results == null) {
                    results = new ArrayList();
                }

                return SearchResultList.list(search, results);
            }
        }

        return null;
    }

    private void initRules (Query query, List<Rule> rules) {
        if (rules != null && rules.size()>0) {
            for (Rule rule : rules) {
                Criteria criteria = getCriteria(rule);
                if (criteria != null) {
                    query.addCriteria(criteria);
                }
            }
        }
    }

    private void initRules (List<AggregationOperation> aggregationOperations, List<Rule> rules) {
        if (rules != null && rules.size()>0) {
            for (Rule rule : rules) {
                Criteria criteria = getCriteria(rule);
                if (criteria != null) {
                    aggregationOperations.add(Aggregation.match(criteria));
                }
            }
        }
    }

    private Criteria getCriteria (Rule rule) {
        if (rule instanceof RuleRelation) {
            Rule[] rules = ((RuleRelation) rule).getRules();

            // 获取多条件
            Criteria[] criterias = new Criteria[rules.length];
            for (int i=0;i<rules.length;i++) {
                criterias[i] = getCriteria(rules[i]);
            }

            // 返回 Criterion 对象
            if (SearchEnum.RuleType.and.equals(rule.getRuleType())) {
                return new Criteria().andOperator(criterias);
            } else if (SearchEnum.RuleType.or.equals(rule.getRuleType())) {
                return new Criteria().orOperator(criterias);
            }
        } else if (rule instanceof RuleNone) {
            String field = ((RuleNone) rule).getField();

            // 返回 Criterion 对象
            if (SearchEnum.RuleType.isEmpty.equals(rule.getRuleType())) {
                return where(field).exists(false);
            } else if (SearchEnum.RuleType.isNotEmpty.equals(rule.getRuleType())) {
                return where(field).exists(true);
            } else if (SearchEnum.RuleType.isNull.equals(rule.getRuleType())) {
                return where(field).exists(false);
            } else if (SearchEnum.RuleType.isNotNull.equals(rule.getRuleType())) {
                return where(field).exists(true);
            }
        } else if (rule instanceof RuleSimple) {
            String field = ((RuleSimple) rule).getField();
            Object value = ((RuleSimple) rule).getData();

            // 返回 Criteria 对象
            if (SearchEnum.RuleType.eq.equals(rule.getRuleType())) {
                return where(field).is(value);
            } else if (SearchEnum.RuleType.ne.equals(rule.getRuleType())) {
                return where(field).ne(value);
            } else if (SearchEnum.RuleType.gt.equals(rule.getRuleType())) {
                return where(field).gt(value);
            } else if (SearchEnum.RuleType.ge.equals(rule.getRuleType())) {
                return where(field).gte(value);
            } else if (SearchEnum.RuleType.lt.equals(rule.getRuleType())) {
                return where(field).lt(value);
            } else if (SearchEnum.RuleType.le.equals(rule.getRuleType())) {
                return where(field).lte(value);
            } else if (SearchEnum.RuleType.in.equals(rule.getRuleType())) {
                if (value instanceof Object[]) {
                    return where(field).in((Object[]) value);
                } else if (value instanceof Collection) {
                    return where(field).in((Collection) value);
                }
            }

        } else if (rule instanceof RuleLike) {
            String field = ((RuleLike) rule).getField();
            Object value = ((RuleLike) rule).getData();

            // 添加查询条件
            if (SearchEnum.RuleType.like.equals(rule.getRuleType())) {
                if (value instanceof String) {
                    return where(field).regex((String) value);
                } else {
                    Assert.state(true, "mongodb 条件查询 like, value 只能为 String 类型");
                }
            } else if (SearchEnum.RuleType.ilike.equals(rule.getRuleType())) {
                Assert.state(true, "mongodb 不支持该条件查询 ilike");
            }
        } else if (rule instanceof RuleSql) {
            String sql = ((RuleSql) rule).getSql();

            Assert.state(true, "mongodb 不支持该条件查询 sql");
        }
        return null;
    }

    private void initPager (Query query, PagePaging page) {
        if (page.getPageNumber() > 0) {
            query.skip((page.getPageNumber() - 1) * page.getPageSize()).limit(page.getPageSize());
        }
    }

    private void initOrder (Query query, Order order) {
        String orderBy = order.getOrderBy();
        SearchEnum.OrderType orderType = order.getOrderType();

        if (StringUtils.isNotEmpty(orderBy) && orderType != null) {
            if (SearchEnum.OrderType.asc.equals(orderType)) {
                query.with(new Sort(Sort.Direction.ASC, orderBy));
            } else {
                query.with(new Sort(Sort.Direction.DESC, orderBy));
            }
        }
    }

}
